CHAPTER 13

Generics

Support for generics is one of the biggest additions to VB 2005 and .NET 2.0. Generics allows you to create open-ended types that are converted into closed types at run time. Each unique closed type is itself a unique type, and only closed types may be instantiated.

Introduction to Generics

Generics creates open-ended types through the use of type parameters. The new Of keyword, followed by a type argument, specifies the type you should use when creating the generic type or executing the generic member at run time. Generics also eliminates the need for boxing and unboxing operations. You can also use generics to increase type safety by specifying type parameter constraints.

To declare a generic type, you specify a list of type parameters in the declaration for which type arguments are given to create closed types, as in the following example:

Public Class MyCollection(Of T)
    Private Storage As T()

    Public Sub New()
    End Sub
End Class

In this case, you declare a generic type, MyCollection(Of T), which treats the type within the collection as an unspecified type. In this example, the type parameter list consists of only one type, and it is described with syntax where the generic type is listed between parentheses. The identifier T is really just a placeholder for any type. At some point, a consumer of MyCollection(Of T) declares what’s called a closed type, by specifying the concrete type that T is supposed to represent. For example, suppose some other assembly wants to create a MyCollection(Of T) constructed type that contains members of type Integer. Then it would do so as shown in the following code:

Public Sub SomeMethod()
    Dim collectionOfNumbers As MyCollection(Of Integer) = New MyCollection(Of Integer)()
End Sub

MyCollection(Of Integer) in the previous code is the closed type. MyCollection (Of Integer) is usable just as any other declared type, and it also follows all of the same rules that other nongeneric types follow. The only difference is that it was born from a generic type. At the point of instantiation, the intermediate language (IL) code behind the implementation of MyCollection(Of T) gets just-in-time (JIT)-compiled in a way that all of the usages of type T in the implementation of MyCollection(Of T) get replaced with type Integer.

Note that all unique constructed types created from the same generic type are, in fact, completely different types that share no implicit conversion capabilities. For example, MyCollection(Of Long) is a completely different type than MyCollection(Of Integer), and you cannot do something like the following:

'THIS WILL NOT WORK!
Public Sub SomeMethod(ByVal intNumbers As MyCollection(Of Integer))
    Dim longNumbers As MyCollection(Of Long) = intNumbers ' ERROR!
End Sub

If you’re familiar with the array covariance rules that allow you to do the following

Public Sub ProcessStrings(ByVal myStrings As String())
    Dim objs As Object() = myStrings

    For Each o As Object In objs
        Console.WriteLine(o)
    Next o
End Sub

then you might be surprised to learn that you cannot accomplish the same idea using constructed generic types. The difference is that with array covariance, the source and the destination of the assignment are of the same type, System.Array. The array covariance rules simply allow you to assign one array from another, as long as the declared type of the elements in the array are implicitly convertible at compile time. However, in the case of two constructed generic types, they are completely separate types.

Efficiency and Type Safety of Generics

Efficiency is arguably one of the greatest gains from generics in VB. A regular array, based on System.Array, can contain a heterogeneous collection of instances created from many types. This does, however, come with its drawbacks. Take a look at the following usage:

Public Sub SomeMethod(ByVal col As ArrayList)
    For Each o As Object In col
        Dim iface As ISomeInterface = CType(o, ISomeInterface)

        o.DoSomething()
    Next o
End Sub

Since everything in the common language runtime (CLR) is derived from System.Object, the ArrayListpassed in via the col parameter could possibly contain a hodgepodge of things. Some of those things may not actually implement ISomeInterface. As you’d expect, an InvalidCastException could erupt from this code. However, wouldn’t it be nice to be able to utilize the compiler’s type engine to help sniff out such things at compile time? That’s exactly what generics allows you to do. Using generics, you can devise something like the following:

Public Sub SomeMethod(ByVal col As IList(Of ISomeInterface))
    For Each iface As ISomeInterface In col
        o.DoSomething()
    Next iface
End Sub

In the previous example, the method accepts an interface of IList(Of T). Since the type parameter to the constructed type is of type ISomeInterface, the only type of objects that the list may hold are those of type ISomeInterface. Now the compiler has everything it needs to enforce strong type safety.

Note: Added type safety at compile time is a good thing, because it’s better to capture bugs based on type mismatches at compile time rather than later at run time.